﻿// <copyright file="ProvidersFactory.cs" company="Todd Aspeotis">
//  Copyright © 2011 Todd Aspeotis
// </copyright>

namespace LocalReportsEngine.Providers
{
    using System;
    using System.Linq;
    using LocalReportsEngine.ExtensionMethods;
    using LocalReportsEngine.ReportDefinitionElements;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class ProvidersFactory
    {
        public static void ResolveData(ResolveDataArgs args)
        {
            ResolveDataSources(args);
            ResolveDataSets(args);
        }

        private static void ResolveDataSets(ResolveDataArgs args)
        {
            ResolveDataSetHandler[] consumerInvocationList = null;

            if (args.OnResolveDataSet != null)
            {
                consumerInvocationList = args.OnResolveDataSet.GetInvocationList() as ResolveDataSetHandler[];
            }

            foreach (var reportDataSet in args.ReportElement.DataSets)
            {
                if (args.DataSets.ContainsKey(reportDataSet.Name))
                {
                    continue;
                }

                object result = null;

                var resolvers = new Func<object>[] {
                    () => ConsumerResolveDataSet(args, reportDataSet, consumerInvocationList),
                    () => FactoryResolveDataSet(args, reportDataSet)
                };

                foreach (var resolver in resolvers)
                {
                    result = resolver();

                    if (result != null)
                    {
                        break;
                    }
                }

                if (result != null)
                {
                    args.DataSets.Add(reportDataSet.Name, result);
                }
            }
        }

        private static object FactoryResolveDataSet(ResolveDataArgs args, DataSetElement dataSet)
        {
            if (dataSet.Query == null)
            {
                return null;
            }

            object dataSourceObject;
            IDataSource dataSource;

            if (args.DataSources.TryGetValue(dataSet.Query.DataSourceName, out dataSourceObject) == false)
            {
                return null;
            }

            dataSource = dataSourceObject as IDataSource;

            if (dataSource == null)
            {
                return null;
            }

            try
            {
                return dataSource.ResolveDataSet(dataSet);
            }
            catch (Exception)
            {
                // Consumer code has raised an exception, swallow it
                return null; // Discard any result
            }
        }

        private static object ConsumerResolveDataSet(ResolveDataArgs args, DataSetElement dataSet, ResolveDataSetHandler[] consumerInvocationList)
        {
            if (consumerInvocationList == null)
            {
                return null;
            }

            ResolveDataSetHandlerArgs handlerArgs = new ResolveDataSetHandlerArgs();
            handlerArgs.DataSetElement = dataSet;
            handlerArgs.ReportElement = args.ReportElement;
            handlerArgs.Name = dataSet.Name;

            foreach (ResolveDataSetHandler handler in consumerInvocationList)
            {
                try
                {
                    handler(args.EventSender, handlerArgs);
                }
                catch (Exception)
                {
                    // Consumer code has raised an exception, swallow it
                    handlerArgs.Result = null; // Discard any result
                    continue;
                }

                if (handlerArgs.Result != null)
                {
                    return handlerArgs.Result;
                }
            }

            return null;
        }

        private static void ResolveDataSources(ResolveDataArgs args)
        {
            ResolveDataSourceHandler[] consumerInvocationList = null;

            if (args.OnResolveDataSource != null)
            {
                consumerInvocationList = args.OnResolveDataSource.GetInvocationList() as ResolveDataSourceHandler[];
            }

            foreach (var reportDataSource in args.ReportElement.DataSources)
            {
                if (args.DataSources.ContainsKey(reportDataSource.Name))
                {
                    continue;
                }

                object result = null;

                var resolvers = new Func<object>[]
                {
                    () => ConsumerResolveDataSource(args, reportDataSource, consumerInvocationList),
                    () => FactoryResolveDataSource(args, reportDataSource)
                };

                foreach (var resolver in resolvers)
                {
                    result = resolver();

                    if (result != null)
                    {
                        break;
                    }
                }

                if (result != null)
                {
                    args.DataSources.Add(reportDataSource.Name, result);
                }
            }
        }

        private static object FactoryResolveDataSource(ResolveDataArgs args, DataSourceElement dataSource)
        {
            if (dataSource.ConnectionProperties == null)
            {
                return null;
            }

            // TODO: Retrieve from cache, which is built in static constructor

            Type type = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(a => a.GetTypes()) // Get all types in all assemblies
                .Where(t => typeof(IDataSource).IsAssignableFrom(t)) // that implement IDataSource
                .Select(t => new { Type = t, Attributes = t.GetCustomAttributes(typeof(DataProviderAttribute), true).Select(a => a as DataProviderAttribute) }) // and build a type/attributes dictionary
                .Where(kvp => kvp.Attributes.Any(a => a.DataProvider.SafeEquals(dataSource.ConnectionProperties.DataProvider))) // Get all types that match our data provider
                .Select(kvp => kvp.Type)
                .FirstOrDefault();

            if (type == null)
            {
                return null;
            }

            try
            {
                IDataSource result = Activator.CreateInstance(type) as IDataSource;
                result.Initialize(dataSource);
                return result;
            }
            catch (Exception)
            {
                // Consumer code has raised an exception, swallow it
                return null; // Discard any result
            }
        }

        private static object ConsumerResolveDataSource(ResolveDataArgs args, DataSourceElement dataSource, ResolveDataSourceHandler[] consumerInvocationList)
        {
            if (consumerInvocationList == null)
            {
                return null;
            }

            ResolveDataSourceHandlerArgs handlerArgs = new ResolveDataSourceHandlerArgs();
            handlerArgs.DataSourceElement = dataSource;
            handlerArgs.ReportElement = args.ReportElement;
            handlerArgs.Name = dataSource.Name;

            foreach (ResolveDataSourceHandler handler in consumerInvocationList)
            {
                try
                {
                    handler(args.EventSender, handlerArgs);
                }
                catch (Exception)
                {
                    // Consumer code has raised an exception, swallow it
                    handlerArgs.Result = null; // Discard any result
                    continue;
                }

                if (handlerArgs.Result != null)
                {
                    return handlerArgs.Result;
                }
            }

            return null;
        }
    }
}
